package com.atguigu.gmall.item.service.impl;

import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.item.feign.SkuDetailFeignClient;
import com.atguigu.gmall.item.service.CacheService;
import com.atguigu.gmall.item.service.SkuDetailService;
import com.atguigu.gmall.product.entity.SkuImage;
import com.atguigu.gmall.product.entity.SkuInfo;
import com.atguigu.gmall.product.entity.SpuSaleAttr;
import com.atguigu.gmall.product.vo.CategoryTreeVo;
import com.atguigu.gmall.product.vo.SkuDetailVo;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

@Service
@Slf4j
public class SkuDetailServiceImpl implements SkuDetailService {

    @Autowired
    SkuDetailFeignClient skuDetailFeignClient;

    @Autowired
    ThreadPoolExecutor executor;

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    CacheService cacheService;

    //JUC 本地锁；在分布式场景中，会导致锁不住所有机器
//    ReentrantLock lock = new ReentrantLock();

    @Autowired
    RedissonClient redissonClient;

    @Override
    public SkuDetailVo getSkuDetailData(Long skuId) {

        //1、先查缓存，命中的话直接返回
        log.info("商品详情查询开始");
        SkuDetailVo returnVal = null;
        returnVal = cacheService.getFromCache(skuId);
        //如果 returnVal ！=null，直接返回，但是有因为位图（布隆过滤器）误判（数据库中实际没有），returnVal是new 出来的一个空对象
        if(returnVal == null) {
            //2、缓存中没有查到，判断位图
            boolean contain = cacheService.mightContain(skuId);
            if(!contain) { //位图中没有
                log.info("bitmap没有，疑似攻击请求，直接打回");
                return null;
            }
            //3、位图中判断有，但缓存没有：回源（在布隆过滤器中有可能存在误判情况（该skuId本来没有，但是与已经存在的skuId在过滤条件上`相等`））
            log.info("bitmap中有，准备回原，需要加锁防止击穿（热点数据突然失效。导致大量请求放给数据库）");
            //准备拦截击穿风险
            RLock lock = redissonClient.getLock(RedisConst.SKU_LOCK + skuId);//锁的粒度更细
            boolean tryLock = lock.tryLock();
            
            if(tryLock) {
                log.info("枷锁成功，正在回源");
                //4、抢锁成功
                returnVal = this.getSkuDetailVoFromRpc(skuId);
                //5、保存到缓存
                //如果这里用的是布隆过滤器（bloomFilter,可能会误判，即使误判，数据库没有也要保存null（保存'x'）值缓存）
                cacheService.save(skuId,returnVal);
                lock.unlock();
            }else {
                //6、抢锁失败,500ms后直接查缓存即可
                try {
                    log.info("加锁失败，等待中，之后查询缓存");
                    TimeUnit.MILLISECONDS.sleep(500);
                    returnVal = cacheService.getFromCache(skuId);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        return returnVal;
    }



    @Override
    public SkuDetailVo getSkuDetailVoFromRpc(Long skuId) {
        SkuDetailVo skuDetailVo = new SkuDetailVo();

        //1、商品详情【图片】
        //异步优化
        CompletableFuture<SkuInfo> skuInfoFuture = CompletableFuture.supplyAsync(() -> {
            log.info("进入商品详情的异步获取");
            SkuInfo skuInfo = skuDetailFeignClient.getSkuInfo(skuId).getData();
            return skuInfo;
        },executor);

        //2、异步：图片
        CompletableFuture<Void> imageFuture = skuInfoFuture.thenAcceptAsync((res) -> {
            log.info("进入图片的异步获取");

            List<SkuImage> skuImages = skuDetailFeignClient.getSkuImages(skuId).getData();

            res.setSkuImageList(skuImages);

            skuDetailVo.setSkuInfo(res);
        },executor);


        //3、异步：当前商品分类的完整信息
        CompletableFuture<Void> categoryFuture = skuInfoFuture.thenAcceptAsync(res -> {
            log.info("进入商品分类的异步获取");

            CategoryTreeVo categoryTreeVo1 = skuDetailFeignClient.getCategoryTreeByCategory3Id(res.getCategory3Id()).getData();
            SkuDetailVo.CategoryViewDto categoryViewDto = this.convertToCategoryViewDto(categoryTreeVo1);

            skuDetailVo.setCategoryView(categoryViewDto);
        },executor);


        //4、异步：实时价格
        CompletableFuture<Void> priceFuture = CompletableFuture.runAsync(() -> {
            log.info("进入实时价格的异步获取");

            BigDecimal price = skuDetailFeignClient.getSkuPrice(skuId).getData();

            skuDetailVo.setPrice(price);
        },executor);


        //5、异步：销售属性
        CompletableFuture<Void> saleAttrFuture = skuInfoFuture.thenAcceptAsync(res -> {
            log.info("进入销售属性的异步获取");

            Long spuId = res.getSpuId();
            List<SpuSaleAttr> spuSaleAttrs = skuDetailFeignClient.getSpuSaleAttr(spuId, skuId).getData();
            skuDetailVo.setSpuSaleAttrList(spuSaleAttrs);
        },executor);


        //6、异步： skuValueJson
        CompletableFuture<Void> valueJsonFuture = skuInfoFuture.thenAcceptAsync(res -> {
            log.info("进入skuValueJson的异步获取");

            String json = skuDetailFeignClient.getValueSkuJson(res.getSpuId()).getData();
            skuDetailVo.setValueSkuJson(json);
        },executor);

        //等待所有任务都完成
        CompletableFuture
                .allOf(valueJsonFuture,saleAttrFuture,priceFuture,categoryFuture,imageFuture)
                .join();


        return skuDetailVo;
    }



    public SkuDetailVo.CategoryViewDto convertToCategoryViewDto(CategoryTreeVo categoryTreeVo1) {
        CategoryTreeVo categoryTreeVo2 = categoryTreeVo1.getCategoryChild().get(0);
        CategoryTreeVo categoryTreeVo3 = categoryTreeVo2.getCategoryChild().get(0);

        SkuDetailVo.CategoryViewDto categoryViewDto = new SkuDetailVo.CategoryViewDto();

        categoryViewDto.setCategory1Id(categoryTreeVo1.getCategoryId());
        categoryViewDto.setCategory1Name(categoryTreeVo1.getCategoryName());

        categoryViewDto.setCategory2Id(categoryTreeVo2.getCategoryId());
        categoryViewDto.setCategory2Name(categoryTreeVo2.getCategoryName());

        categoryViewDto.setCategory3Id(categoryTreeVo3.getCategoryId());
        categoryViewDto.setCategory3Name(categoryTreeVo3.getCategoryName());

        return categoryViewDto;
    }
}
